/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.jellyfishumbrella.GL;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.util.glu.GLU.*;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

import com.jellyfishumbrella.GameLogic;

/**
 * 
 * @author 500441547
 */
public class RendererGL {
	public static final String					WINDOW_TITLE			= "TOTEM";
	public static final int						DISPLAY_WIDTH			= 1920 / 2;
	public static final int						DISPLAY_HEIGHT			= 1080 / 2;

	public static final int						RENDERSTYLE_COMPILED	= 0;
	public static final int						RENDERSTYLE_VBO			= 1;
	public static int							renderstyle				= RENDERSTYLE_COMPILED;

	public static final int						SEED					= (int) (32767 * Math.random());

	public static GameLogic						gameLogic;

	public static Camera						camera;
	public static Hud							hud;
	public static Lighting						lighting;
	public static Sun							sun;
	public static MyFog							fog;

	public static Matrix4f						projectionMatrix;
	public static Matrix4f						identityMatrix;

	public float								GRAVITY					= -0.1f;

	public static Map<Integer, Selectable>		selectables;
	public IntBuffer							selectionBuffer			= BufferUtils.createIntBuffer(512);

	public static LinkedBlockingQueue<MeshGL>	renderables;

	public RendererGL() throws LWJGLException {
		System.out.println("making engine: " + SEED);
		createDisplay();
	}

	public void connectToLogic(GameLogic gameLogic) throws LWJGLException {
		this.gameLogic = gameLogic;
		gameLogic.renderGL = this;

		initGL();
		resizeGL();
		renderables = new LinkedBlockingQueue<MeshGL>();

		create();
	}

	public void createDisplay() throws LWJGLException {
		// Display
		Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH, DISPLAY_HEIGHT));
		Display.setFullscreen(false);
		Display.setVSyncEnabled(true);
		Display.setTitle(WINDOW_TITLE);
		Display.create();
	}

	public void initGL() {
		projectionMatrix = new Matrix4f();
		identityMatrix = new Matrix4f();

		glClearColor(0.3f, 0.3f, 0.6f, 1.0f);
		glClearDepth(1.0f);
		glShadeModel(GL_SMOOTH);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDepthFunc(GL_LEQUAL);
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_NORMALIZE);
		GL_Enables();
		// glActiveTexture(GL_TEXTURE0); // hex positions
		// glEnable(GL_TEXTURE_2D);
		// glActiveTexture(GL_TEXTURE1); // hex data
		// glEnable(GL_TEXTURE_3D);
		// glActiveTexture(GL_TEXTURE2); // lighting A
		// glEnable(GL_TEXTURE_3D);
		// glActiveTexture(GL_TEXTURE3); // lighting B
		// glEnable(GL_TEXTURE_3D);

		System.out.println("max texture size = " + GL_MAX_3D_TEXTURE_SIZE);
		System.out.println("max num of textures = " + GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
	}

	public static void GL_Enables() {
		glEnable(GL_LIGHTING);
		// glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,
		// GL_SEPARATE_SPECULAR_COLOR);
		// glEnable(GL_COLOR_MATERIAL);
		// glColorMaterial ( GL_FRONT_AND_BACK, GL_AMBIENT ) ;
		glEnable(GL_DEPTH_TEST);
	}

	public static void GL_Disables() {
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
	}

	public void resizeGL() {
		glViewport(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		// gluPerspective(camera.FOV,DISPLAY_WIDTH/DISPLAY_HEIGHT,0.01f,10000.0f);
		gluPerspective(camera.FOV, (float) DISPLAY_WIDTH / (float) DISPLAY_HEIGHT, 0.01f, 1000000.0f);

		glPushMatrix();

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glPushMatrix();
	}

	public Matrix4f gl4Perspective(float viewAngle, float aspectRatio, float nearZ, float farZ) {
		float ymax, xmax;
		float temp, temp2, temp3, temp4;
		ymax = nearZ * (float) Math.tan(viewAngle * Math.PI / 360.0f);
		xmax = ymax * aspectRatio;

		float a = nearZ / xmax;
		float b = nearZ / ymax;
		float c = -(farZ + nearZ) / (farZ - nearZ);
		float d = (-2.0f * farZ * nearZ) / (farZ - nearZ);

		float[] matrix = { a, 0.0f, 0.0f, 0.0f, 0.0f, b, 0.0f, 0.0f, 0.0f, 0.0f, c, d, 0.0f, 0.0f, -1.0f, 0.0f };
		return null;// matrix;
	}

	public void render(LandscapeGLMesh islandGL) {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		
		camera.render();
		// System.out.println(camera._pos+"|"+camera._rot);

		// lighting.render();
		sun.render();

		islandGL.renderAdv();
		// testRender();

		hud.render();
	}

	public void testRender() {
		glPushMatrix();
		glTranslatef(0, 0, 0);
		glColor3f(0.5f, 0.5f, 1.0f); // Set The Color To Blue One Time Only
		glBegin(GL_TRIANGLES); // Drawing Using Triangles
		glColor3f(1.0f, 0.0f, 0.0f); // Set The Color To Red
		glVertex3f(0.0f, 100.0f, 0.0f); // Top
		glColor3f(0.0f, 1.0f, 0.0f); // Set The Color To Green
		glVertex3f(-100.0f, -100.0f, 0.0f); // Bottom Left
		glColor3f(0.0f, 0.0f, 100.0f); // Set The Color To Blue
		glVertex3f(100.0f, -100.0f, 0.0f); // Bottom Right
		glEnd(); // Finished Drawing The Triangle
		glTranslatef(300.0f, 0.0f, 0.0f); // Move Right 3 Units
		glColor3f(0.5f, 0.5f, 1.0f); // Set The Color To Blue One Time Only
		glBegin(GL_QUADS); // Draw A Quad
		glVertex3f(-100.0f, 100.0f, 0.0f); // Top Left
		glVertex3f(100.0f, 100.0f, 0.0f); // Top Right
		glVertex3f(100.0f, -100.0f, 0.0f); // Bottom Right
		glVertex3f(-100.0f, -100.0f, 0.0f); // Bottom Left
		glEnd(); // Done Drawing The Quad
		glPopMatrix();
	}

	public void create() throws LWJGLException {
		camera = new Camera();

		// Lighting.addDistant(new Vector3f(0.0f, 0.0f, 2.0f), new float[] {
		// 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new
		// float[] { 1.0f, 1.0f, 1.0f, 1.0f });
		// Lighting.addPoint(new Vector3f(1000.0f, 1000.0f, 0.0f), new float[] {
		// 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new
		// float[] { 1.0f, 1.0f, 1.0f, 1.0f });
		
		sun = new Sun();
		sun.addSun(new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f });

		// fog = new MyFog();

		hud = new Hud();

		// Keyboard
		Keyboard.create();

		// Mouse
		Mouse.setGrabbed(true);
		Mouse.create();

	}

	public void destroy() {
		// Methods already check if created before destroying.
		Mouse.destroy();
		Keyboard.destroy();
		Display.destroy();
		// gameLogic.stopThreads();
	}

	/**
	 * The selection magic happens here.
	 * 
	 * @param mouse_x
	 * @param mouse_y
	 */
	public static Vector3f screenTo3D(int screenX, int screenY) {
		IntBuffer viewport = BufferUtils.createIntBuffer(16);
		FloatBuffer modelview = BufferUtils.createFloatBuffer(16);
		FloatBuffer projection = BufferUtils.createFloatBuffer(16);
		FloatBuffer winZ = BufferUtils.createFloatBuffer(1);
		float winX, winY;
		FloatBuffer position = BufferUtils.createFloatBuffer(3);

		glGetFloat(GL_MODELVIEW_MATRIX, modelview);
		glGetFloat(GL_PROJECTION_MATRIX, projection);
		glGetInteger(GL_VIEWPORT, viewport);

		winX = (float) screenX;
		winY = (float) viewport.get(3) - (float) screenY;

		// glReadPixels(screenX, (int)winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
		// winZ);
		glReadPixels(screenX, screenY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, winZ);

		gluUnProject(winX, winY, winZ.get(), modelview, projection, viewport, position);
		Vector3f wPos = new Vector3f(position.get(0), position.get(1), position.get(2));
		return wPos;
	}
}
